两难!到底用Apache BeanUtils还是Spring BeanUtils?
# 前言
在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息,比如DTO数据传输对象和数据对象DO,我们需要将DO对象进行属性复制到DTO,但是对象格式又不一样,所以我们需要编写映射代码将对象中的属性值从一种类型转换成另一种类型。
# 对象拷贝
在具体介绍两种 BeanUtils 之前,先来补充一些基础知识。它们两种工具本质上就是对象拷贝工具,而对象拷贝又分为深拷贝和浅拷贝,下面进行详细解释。
# 什么是浅拷贝和深拷贝
简单来说:
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
# BeanUtils
前面简单讲了一下对象拷贝的一些知识,下面就来具体看下两种 BeanUtils 工具
# Apache 的 BeanUtils
首先来看一个非常简单的BeanUtils的例子
publicclass PersonSource {
private Integer id;
private String username;
private String password;
private Integer age;
// getters/setters omiited
}
publicclass PersonDest {
private Integer id;
private String username;
private Integer age;
// getters/setters omiited
}
publicclass TestApacheBeanUtils {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
//下面只是用于单独测试
PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21);
PersonDest personDest = new PersonDest();
BeanUtils.copyProperties(personDest,personSource);
System.out.println("persondest: "+personDest);
}
}
persondest: PersonDest{id=1, username='pjmike', age=21}
//将源对象中的值拷贝到目标对象//将源对象中的值拷贝到目标对象
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
但是由于 Apache下的BeanUtils对象拷贝性能太差,不建议使用,而且在阿里巴巴Java开发规约插件上也明确指出:
Ali-Check | 避免用Apache Beanutils进行属性的copy。
commons-beantutils 对于对象拷贝加了很多的检验,包括类型的转换,甚至还会检验对象所属的类的可访问性,可谓相当复杂,这也造就了它的差劲的性能,具体实现代码如下:
public void copyProperties(final Object dest, final Object orig)
throws IllegalAccessException, InvocationTargetException {
// Validate existence of the specified beans
if (dest == null) {
thrownew IllegalArgumentException
("No destination bean specified");
}
if (orig == null) {
thrownew IllegalArgumentException("No origin bean specified");
}
if (log.isDebugEnabled()) {
log.debug("BeanUtils.copyProperties(" + dest + ", " +
orig + ")");
}
// Copy the properties, converting as necessary
if (orig instanceof DynaBean) {
final DynaProperty[] origDescriptors =
((DynaBean) orig).getDynaClass().getDynaProperties();
for (DynaProperty origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
// Need to check isReadable() for WrapDynaBean
// (see Jira issue# BEANUTILS-61)
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
final Object value = ((DynaBean) orig).get(name);
copyProperty(dest, name, value);
}
}
} elseif (orig instanceof Map) {
@SuppressWarnings("unchecked")
final
// Map properties are always of type <String, Object>
Map<String, Object> propMap = (Map<String, Object>) orig;
for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
final String name = entry.getKey();
if (getPropertyUtils().isWriteable(dest, name)) {
copyProperty(dest, name, entry.getValue());
}
}
} else/* if (orig is a standard JavaBean) */ {
final PropertyDescriptor[] origDescriptors =
getPropertyUtils().getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
if ("class".equals(name)) {
continue; // No point in trying to set an object's class
}
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
try {
final Object value =
getPropertyUtils().getSimpleProperty(orig, name);
copyProperty(dest, name, value);
} catch (final NoSuchMethodException e) {
// Should not happen
}
}
}
}
}
# Spring 的 BeanUtils
publicclass TestSpringBeanUtils {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
//下面只是用于单独测试
PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21);
PersonDest personDest = new PersonDest();
BeanUtils.copyProperties(personSource,personDest);
System.out.println("persondest: "+personDest);
}
}
Spring下的BeanUtils也是使用 copyProperties方法进行拷贝,只不过它的实现方式非常简单,就是对两个对象中相同名字的属性进行简单的get/set,仅检查属性的可访问性。具体实现如下:
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
# 小结
以上简要的分析两种BeanUtils,因为Apache下的BeanUtils性能较差,不建议使用,可以使用 Spring的BeanUtils ,或者使用其他拷贝框架,比如:Dozer、ModelMapper等等
作者:pjmike来源:https://urlify.cn/vUfIry
往期推荐
🔗
重磅!2020中国高校毕业生月薪排名:清华第1,24高校过万,你呢? 牛逼!终于有人开源了一份基于SSM框架实现了支付宝支付功能的完整源代码... 阿里面试官:你读过Dubbo的源码,给我说说服务之间是怎么进行消费的?
点击阅读原文,获得更多精彩内容